package cn.xf.districonfig.client.rpc;

import cn.xf.districonfig.client.processor.SpringValueProcessor;
import cn.xf.districonfig.client.scope.RefreshScopeRegistry;
import cn.xf.districonfig.client.scope.XfPropertyPlaceholderConfigurer;
import cn.xf.districonfig.client.util.RpcUtils;
import com.alibaba.fastjson.JSONObject;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.env.OriginTrackedMapPropertySource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class RpcEnvCache implements ApplicationContextAware, DisposableBean, BeanPostProcessor {

    private Logger logger = LoggerFactory.getLogger(RpcEnvCache.class);

    private String serverAddr;

    private ConcurrentHashMap<String, Object> cacheMap = new ConcurrentHashMap<>();

    private ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1,
            new ThreadFactoryBuilder().setNameFormat("thread-schedule-rpc-%d").setDaemon(true).build());

    private ApplicationContext applicationContext;

    private Environment environment;

    private List<String> serverAddresses = new ArrayList<>();

    private String zkName = "yrConfig";

    private volatile boolean started = true;

    private SpringValueProcessor springValueProcessor;

    @PostConstruct
    public void init() {

        logger.info("Start init!");

        this.serverAddr = environment.getProperty("xf.conf.server-addr");
        this.springValueProcessor = applicationContext.getBean(SpringValueProcessor.class);
        if (StringUtils.isBlank(serverAddr)) {
            throw new RuntimeException("property xf.conf.server-addr cannot be null!");
        }
        logger.info("Server addresses are {}", serverAddr);

        if (StringUtils.isNotBlank(serverAddr)) {
            serverAddresses.addAll(Arrays.asList(serverAddr.split(",")));
        }
        loadCache();
        scheduledExecutorService.scheduleWithFixedDelay(() -> {
            try {
                childCache();
            } catch (Exception e) {
                e.printStackTrace();
            }

            }, 1 ,30, TimeUnit.SECONDS);
    }

    private void childCache() {
        logger.info("Start run!");
        while (started) {

            try {
                Thread.sleep(30000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Map<String, Object> cacheNode = getCacheNode();
            if (cacheNode == null) {
                continue;
            }
            if (!chectExistsInSpring()) {
                createProperty();
            } else {
                updateProperties(cacheNode);
            }
            refreshBean();
        }

    }

    private void updateProperties(Map<String, Object> cacheNode) {

        cacheNode.forEach((k, v)-> {
            Object value = v != null ? v : "";
            if (cacheMap.containsKey(k)) {

                Object formerValue = cacheMap.get(k);
                if (!formerValue.equals(value)) {
                    cacheMap.put(k, value);
                }
            } else {
                cacheMap.put(k, value);
            }
            springValueProcessor.updateValue(k, value);
        });

        ConfigurableEnvironment configurableEnvironment = (ConfigurableEnvironment) environment;
        MutablePropertySources propertySources = configurableEnvironment.getPropertySources();
        PropertySource<?> propertySource = propertySources.get(zkName);
        ConcurrentHashMap<String, Object> zkMap = (ConcurrentHashMap<String, Object>) propertySource.getSource();
        zkMap.putAll(cacheMap);
    }

    private Map<String, Object> getCacheNode() {
        for (String server : serverAddresses) {

            JSONObject jsonObject = new JSONObject();
            jsonObject.put("env", "test");
            Map<String, Object> respObj = RpcUtils.find(server, jsonObject.toJSONString());
            // parse
            if (respObj != null && respObj.containsKey("data")) {
                Object datao = respObj.get("data");
                if (!(datao instanceof Map)) {
                    return null;
                }
                Map<String, Object> data = (Map<String, Object>) respObj.get("data");
                return data;
            }
        }
        return null;
    }

    private void loadCache() {
        Map<String, Object> cacheNode = getCacheNode();
        if (cacheNode == null) {
            return;
        }
        cacheNode.forEach((k, v) -> {
            if (k != null) {
                cacheMap.put(k, v != null ? v : "");
            }
        });
        cacheMap.forEach((k, v) -> {
            logger.info("k: {}, v: {}", k, v);
        });
        loadBeanConfig();
    }

    private void loadBeanConfig() {

        createProperty();
    }

    private void createProperty() {
        if (!(environment instanceof ConfigurableEnvironment)) {
            return;
        }
        ConfigurableEnvironment configurableEnvironment = (ConfigurableEnvironment) environment;
        MutablePropertySources propertySources = configurableEnvironment.getPropertySources();

        OriginTrackedMapPropertySource zkSource = new OriginTrackedMapPropertySource(zkName, cacheMap);
        propertySources.addFirst(zkSource);
    }

    private boolean chectExistsInSpring() {

        if (!(environment instanceof ConfigurableEnvironment)) {
            return false;
        }
        ConfigurableEnvironment configurableEnvironment = (ConfigurableEnvironment) environment;
        MutablePropertySources propertySources = configurableEnvironment.getPropertySources();
        for (PropertySource<?> propertySource : propertySources) {

            if (zkName.equalsIgnoreCase(propertySource.getName())) {
                return true;
            }
        }

        return false;
    }

    private void refreshBean() {


    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

        if (bean instanceof XfPropertyPlaceholderConfigurer) {
            System.out.println(cacheMap.toString());
            System.out.println(beanName);
        }
        return bean;
    }

    public ConcurrentHashMap<String, Object> getCacheMap() {
        return cacheMap;
    }

    @Override
    public void destroy() throws Exception {
        started = false;
        scheduledExecutorService.shutdown();
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
        this.environment = applicationContext.getEnvironment();
    }
}
